package it.eng.area118.mdocommon.android.util;

import it.eng.area118.mdocommon.android.model.AndroidModelContainer;

import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

public class DateUtils {

	public final static int SEMI_MONTH = 1001;

	public static final String PATTERN_ISO8601_UTC = "dd/MM/yyyy'T'HH:mm:ss'+0000'";

	private static final int[][] fields = { { Calendar.MILLISECOND },
			{ Calendar.SECOND }, { Calendar.MINUTE },
			{ Calendar.HOUR_OF_DAY, Calendar.HOUR },
			{ Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM },
			{ Calendar.MONTH, SEMI_MONTH }, { Calendar.YEAR }, { Calendar.ERA } };

	public static boolean isSameDay(Date date1, Date date2) {
		if (date1 == null || date2 == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar cal1 = Calendar.getInstance();
		cal1.setTime(date1);
		Calendar cal2 = Calendar.getInstance();
		cal2.setTime(date2);
		return isSameDay(cal1, cal2);
	}

	public static boolean isSameDay(Calendar cal1, Calendar cal2) {
		if (cal1 == null || cal2 == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
				&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1
					.get(Calendar.DAY_OF_YEAR) == cal2
				.get(Calendar.DAY_OF_YEAR));
	}

	public static boolean isSameInstant(Date date1, Date date2) {
		if (date1 == null || date2 == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		return date1.getTime() == date2.getTime();
	}

	public static boolean isSameInstant(Calendar cal1, Calendar cal2) {
		if (cal1 == null || cal2 == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		return cal1.getTime().getTime() == cal2.getTime().getTime();
	}

	public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) {
		if (cal1 == null || cal2 == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		return (cal1.get(Calendar.MILLISECOND) == cal2
				.get(Calendar.MILLISECOND)
				&& cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
				&& cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE)
				&& cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR)
				&& cal1.get(Calendar.DAY_OF_YEAR) == cal2
						.get(Calendar.DAY_OF_YEAR)
				&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
				&& cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && cal1
					.getClass() == cal2.getClass());
	}

	public static Date parseDate(String str, String[] parsePatterns)
			throws ParseException {
		if (str == null || parsePatterns == null) {
			throw new IllegalArgumentException("NULL DATE OR PATTERNS");
		}

		SimpleDateFormat parser = null;
		ParsePosition pos = new ParsePosition(0);
		for (int i = 0; i < parsePatterns.length; i++) {
			if (i == 0) {
				parser = new SimpleDateFormat(parsePatterns[0]);
			} else {
				parser.applyPattern(parsePatterns[i]);
			}
			pos.setIndex(0);
			Date date = parser.parse(str, pos);
			if (date != null && pos.getIndex() == str.length()) {
				return date;
			}
		}
		throw new ParseException("DATE PARSING FAILURE: " + str, -1);
	}

	public static Date addYears(Date date, int amount) {
		return add(date, Calendar.YEAR, amount);
	}

	public static Date addMonths(Date date, int amount) {
		return add(date, Calendar.MONTH, amount);
	}

	public static Date addWeeks(Date date, int amount) {
		return add(date, Calendar.WEEK_OF_YEAR, amount);
	}

	public static Date addDays(Date date, int amount) {
		return add(date, Calendar.DAY_OF_MONTH, amount);
	}

	public static Date addHours(Date date, int amount) {
		return add(date, Calendar.HOUR_OF_DAY, amount);
	}

	public static Date addMinutes(Date date, int amount) {
		return add(date, Calendar.MINUTE, amount);
	}

	public static Date addSeconds(Date date, int amount) {
		return add(date, Calendar.SECOND, amount);
	}

	private static Date add(Date date, int calendarField, int amount) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(calendarField, amount);
		return c.getTime();
	}

	public static Date setYears(Date date, int amount) {
		return set(date, Calendar.YEAR, amount);
	}

	public static Date setMonths(Date date, int amount) {
		return set(date, Calendar.MONTH, amount);
	}

	public static Date setDays(Date date, int amount) {
		return set(date, Calendar.DAY_OF_MONTH, amount);
	}

	public static Date setHours(Date date, int amount) {
		return set(date, Calendar.HOUR_OF_DAY, amount);
	}

	public static Date setMinutes(Date date, int amount) {
		return set(date, Calendar.MINUTE, amount);
	}

	public static Date setSeconds(Date date, int amount) {
		return set(date, Calendar.SECOND, amount);
	}

	private static Date set(Date date, int calendarField, int amount) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar c = Calendar.getInstance();
		c.setLenient(false);
		c.setTime(date);
		c.set(calendarField, amount);
		return c.getTime();
	}

	public static Date round(Date date, int field) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar gval = Calendar.getInstance();
		gval.setTime(date);
		modify(gval, field, true);
		return gval.getTime();
	}

	public static Calendar round(Calendar date, int field) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar rounded = (Calendar) date.clone();
		modify(rounded, field, true);
		return rounded;
	}

	public static Date truncate(Date date, int field) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar gval = Calendar.getInstance();
		gval.setTime(date);
		modify(gval, field, false);
		return gval.getTime();
	}

	public static Calendar truncate(Calendar date, int field) {
		if (date == null) {
			throw new IllegalArgumentException("NULL DATE");
		}
		Calendar truncated = (Calendar) date.clone();
		modify(truncated, field, false);
		return truncated;
	}

	private static void modify(Calendar val, int field, boolean round) {
		if (val.get(Calendar.YEAR) > 280000000) {
			throw new ArithmeticException("INVALID CALENDAR VALUE");
		}

		if (field == Calendar.MILLISECOND) {
			return;
		}

		Date date = val.getTime();
		long time = date.getTime();
		boolean done = false;

		int millisecs = val.get(Calendar.MILLISECOND);
		if (!round || millisecs < 500) {
			time = time - millisecs;
		}
		if (field == Calendar.SECOND) {
			done = true;
		}

		int seconds = val.get(Calendar.SECOND);
		if (!done && (!round || seconds < 30)) {
			time = time - (seconds * 1000L);
		}
		if (field == Calendar.MINUTE) {
			done = true;
		}

		int minutes = val.get(Calendar.MINUTE);
		if (!done && (!round || minutes < 30)) {
			time = time - (minutes * 60000L);
		}

		// reset time
		if (date.getTime() != time) {
			date.setTime(time);
			val.setTime(date);
		}

		boolean roundUp = false;
		for (int i = 0; i < fields.length; i++) {
			for (int j = 0; j < fields[i].length; j++) {
				if (fields[i][j] == field) {
					if (round && roundUp) {
						if (field == DateUtils.SEMI_MONTH) {
							if (val.get(Calendar.DATE) == 1) {
								val.add(Calendar.DATE, 15);
							} else {
								val.add(Calendar.DATE, -15);
								val.add(Calendar.MONTH, 1);
							}
						} else {
							val.add(fields[i][0], 1);
						}
					}
					return;
				}
			}
			int offset = 0;
			boolean offsetSet = false;
			switch (field) {
			case DateUtils.SEMI_MONTH:
				if (fields[i][0] == Calendar.DATE) {
					offset = val.get(Calendar.DATE) - 1;
					if (offset >= 15) {
						offset -= 15;
					}
					roundUp = offset > 7;
					offsetSet = true;
				}
				break;
			case Calendar.AM_PM:
				if (fields[i][0] == Calendar.HOUR_OF_DAY) {
					offset = val.get(Calendar.HOUR_OF_DAY);
					if (offset >= 12) {
						offset -= 12;
					}
					roundUp = offset > 6;
					offsetSet = true;
				}
				break;
			}
			if (!offsetSet) {
				int min = val.getActualMinimum(fields[i][0]);
				int max = val.getActualMaximum(fields[i][0]);
				offset = val.get(fields[i][0]) - min;
				roundUp = offset > ((max - min) / 2);
			}
			if (offset != 0) {
				val.set(fields[i][0], val.get(fields[i][0]) - offset);
			}
		}
		throw new IllegalArgumentException("UNSUPPORTED FIELD : " + field);
	}

	public static String format(Date date, String pattern) {
		return format(date, pattern, null, null);
	}

	public static String format(Date date, String pattern, TimeZone timeZone,
			Locale locale) {
		SimpleDateFormat df = null;
		if (locale != null) {
			df = new SimpleDateFormat(pattern, locale);
		} else {
			df = new SimpleDateFormat(pattern);
		}
		if (timeZone != null) {
			df.setTimeZone(timeZone);
		}
		return df.format(date);
	}

	public static SystemTime getLocalSystemTime() {
		return getSystemTime(TimeZone.getDefault());
	}

	public static SystemTime getUTCSystemTime() {
		return getSystemTime(TimeZone.getTimeZone("UTC"));
	}

	public static SystemTime getSystemTime(TimeZone tz) {
		Calendar cal = GregorianCalendar.getInstance(tz);
		Calendar now = (Calendar) AndroidModelContainer.getInstance().getModel(
				"CURRENT_TIME");
		cal.setTime(now.getTime());
		return cal2sys(cal);
	}

	public static SystemTime getLocalSystemTime(long timestamp) {
		return getSystemTime(timestamp, TimeZone.getDefault());
	}

	public static SystemTime getSystemTime(long timestamp, TimeZone tz) {
		Calendar cal = GregorianCalendar.getInstance(tz);
		cal.setTimeInMillis(timestamp);

		return cal2sys(cal);
	}

	public static SystemTime cal2sys(Calendar cal) {
		SystemTime st = new SystemTime();
		st.setDay((short) cal.get(Calendar.DAY_OF_MONTH));
		st.setMonth((short) (cal.get(Calendar.MONTH) + 1));
		st.setYear((short) cal.get(Calendar.YEAR));
		st.setHour((short) cal.get(Calendar.HOUR_OF_DAY));
		st.setMinutes((short) cal.get(Calendar.MINUTE));
		st.setSeconds((short) cal.get(Calendar.SECOND));
		st.setOffset((short) (cal.get(Calendar.ZONE_OFFSET) / 3600000 + cal
				.get(Calendar.DST_OFFSET) / 3600000));
		return st;
	}

	public static SystemTime getUTCSystemTime(long timestamp) {
		return getSystemTime(timestamp, TimeZone.getTimeZone("UTC"));
	}

	public static String format(Date date, String pattern, TimeZone timezone) {
		SimpleDateFormat sdf = new SimpleDateFormat(pattern);
		sdf.setTimeZone(timezone);
		String output = null;
		try {
			return sdf.format(date);
		} catch (Exception e) {
		}
		return output;
	}

}
